﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using System.Web.Security;
using System.Web;

namespace Buggy.Web
{
	// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "BuggyService" in code, svc and config file together.
	[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
	//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] 
	public class BuggyService : IBuggyService
	{
		#region IBuggyService Members

		public string Test(string input)
		{
			return "OK: " + input;			
		}		

		public LoginStatusEnum Login(string login, string password)
		{
			if (IsAuthenticated())
				return LoginStatusEnum.AlreadyLogined;

			if (!isUserPasswordPairExists(login, password))
				return LoginStatusEnum.WrongLoginOrPassword;

			// Log the user into the site
			FormsAuthenticationTicket ticket = getLoginTicket(login);

			// Encrypt the ticket.
			string encTicket = FormsAuthentication.Encrypt(ticket);

			// Create the cookie.
			HttpContext.Current.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
			return LoginStatusEnum.OK;
		}

        private bool isUserPasswordPairExists(string login, string password)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
			var users = from user in ctx.Users 
                        where ((user.login == login) && (user.password == password)) 
                        select user;
			return (users.Count() == 1);
        }

        private FormsAuthenticationTicket getLoginTicket(string login)
        {
            return new FormsAuthenticationTicket(
				1,				
				login,
				DateTime.Now,
				DateTime.Now.AddHours(1),
				false,
				"Role One",
				FormsAuthentication.FormsCookiePath);
        }

		public LoginStatusEnum Logout()
		{
			if (!IsAuthenticated())
				return LoginStatusEnum.NotLogined;

			FormsAuthentication.SignOut();
			return LoginStatusEnum.OK;
		}

		public bool IsAuthenticated()
		{
			return (HttpContext.Current.User.Identity.IsAuthenticated);
		}

		public List<UserInfo> GetRelatedUsersByBug(int bugId)
		{
			BuggyDataEntities ctx = new BuggyDataEntities();
			var bouList = from bou in ctx.BugsOnUsers where bou.bugId == bugId select bou;
			var userList = ctx.Users.ToList();
			return UserInfo.init(userList, bouList.ToList());
		}

		public bool CreateBug(int categoryId, string name, string description)
		{
			Users user = getCurrentUser();
			if (user == null)
				return false;

			BuggyDataEntities ctx = new BuggyDataEntities();
			Bugs newbug = ctx.Bugs.CreateObject();
			newbug.categoryId = categoryId;
			newbug.status = Enum.GetName(typeof(BugStatus), BugStatus.active);
			newbug.name = name;
			newbug.description = description;
			newbug.time = DateTime.Now;
			ctx.Bugs.AddObject(newbug);
			ctx.SaveChanges();			

			BugsOnUsers bou = ctx.BugsOnUsers.CreateObject();
			bou.bugId = newbug.id;
			bou.userId = user.id;
			bou.access = Enum.GetName(typeof(BugAccess), BugAccess.own);
			ctx.BugsOnUsers.AddObject(bou);
			ctx.SaveChanges();

			return true;
		}

        public void AddUserToBug(string username, int bugid)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            Users user = CheckBeforeAddUserToBug(ctx, username, bugid);

            BugsOnUsers bou = ctx.BugsOnUsers.CreateObject();
            bou.bugId = bugid;
            bou.userId = user.id;
            bou.access = Enum.GetName(typeof(BugAccess), BugAccess.read);
            ctx.BugsOnUsers.AddObject(bou);
            ctx.SaveChanges();
        }

        public void RemoveUserFromBug(string username, int bugid)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            Users user = CheckBeforeRemoveUserFromBug(ctx, username, bugid);
            BugsOnUsers bou = getBugOnUser(ctx, user, bugid);
            if (bou == null)
                throw new FaultException("Connection with bug and user is not found");
            ctx.BugsOnUsers.DeleteObject(bou);
            ctx.SaveChanges();
        }

		#endregion

        protected void CheckUsersAndBug(Users user, Users current_user, Bugs bug)
        {
            if (user == null)
                throw new FaultException("User with specified name not found");
            if (current_user == null)
                throw new FaultException("You are not logged in");
            if (bug == null)
                throw new FaultException("Bug with given id not found");
            if (!isUserOwnerOfBug(current_user, bug))
                throw new FaultException("Current user is not the owner of this bug");
        }

        protected Users CheckBeforeAddUserToBug(BuggyDataEntities ctx, string username, int bugid)
        {
            if (username == getCurrentUsername())
                throw new FaultException("Current user already owns this bug");
            Users user = getUserByName(username);
            Users current_user = getCurrentUser();
            Bugs bug = getBugById(bugid);
            CheckUsersAndBug(user, current_user, bug);           
            if (isBugBelongToUser(user))
                throw new FaultException("Bug is already belonged to user");
            return user;
        }

        protected Users CheckBeforeRemoveUserFromBug(BuggyDataEntities ctx, string username, int bugid)
        {
            if (username == getCurrentUsername())
                throw new FaultException("Current user owns this bug. Can't delete owner");
            Users user = getUserByName(username);
            Users current_user = getCurrentUser();
            Bugs bug = getBugById(bugid);
            CheckUsersAndBug(user, current_user, bug);
            if (!isBugBelongToUser(user))
                throw new FaultException("Bug is not belonged to user");
            return user;
        }

        protected Bugs getBugById(int bugid)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            var bugs = from bug in ctx.Bugs
                       where bug.id == bugid
                       select bug;
            if (bugs.Count() == 0)
                return null;
            else
                return bugs.First();
        }

        protected bool isBugBelongToUser(Users user)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            string edit = Enum.GetName(typeof(BugAccess), BugAccess.edit);
            string read = Enum.GetName(typeof(BugAccess), BugAccess.read);
            var bous = from bou in ctx.BugsOnUsers
                       where bou.userId == user.id && 
                             (bou.access == read || bou.access == edit)
                       select bou;
            return (bous.Count() == 1);
        }

        protected bool isUserOwnerOfBug(string userName, int bugid)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            string own = Enum.GetName(typeof(BugAccess), BugAccess.own);
            var bous = from bou in ctx.BugsOnUsers 
                       join us in ctx.Users on bou.userId equals us.id
                       where us.login == userName &&
                             bou.bugId == bugid && 
                             bou.access == own
                       select bou;
            return (bous.Count() == 1);
        }

        protected bool isUserOwnerOfBug(Users user, int bugid)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            string own = Enum.GetName(typeof(BugAccess), BugAccess.own);
            var bous = from bou in ctx.BugsOnUsers
                       where bou.userId == user.id && 
                             bou.bugId == bugid && 
                             bou.access == own
                       select bou;
            return (bous.Count() == 1);
        }

        protected bool isUserOwnerOfBug(Users user, Bugs bug)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            string own = Enum.GetName(typeof(BugAccess), BugAccess.own);
            var bous = from bou in ctx.BugsOnUsers
                       where bou.userId == user.id &&
                             bou.bugId == bug.id &&
                             bou.access == own
                       select bou;
            return (bous.Count() == 1);
        }

        protected BugsOnUsers getBugOnUser(BuggyDataEntities ctx, Users user, int bugid)
        {
            var bous = from bou in ctx.BugsOnUsers
                       where bou.bugId == bugid && bou.userId == user.id
                       select bou;
            return (bous.Count() == 1 ? bous.First() : null);
        }

        protected Users getUserByName(string userName)
        {
            BuggyDataEntities ctx = new BuggyDataEntities();
            var users = from user in ctx.Users
                        where user.login == userName
                        select user;
            if (users.Count() == 0)
                return null;
            else 
                return users.First();
        }

		protected string getCurrentUsername()
		{
			return (HttpContext.Current.User.Identity.Name);
		}

		protected Users getCurrentUser()
		{
			string userName = getCurrentUsername();
			return getUserByName(userName);
		}
    }

	[DataContract]
	public class UserInfo
	{
		[DataMember]
		public int userId;
		[DataMember]
		public string userLogin;

		[DataMember]
		public bool isRelatedToCurrentBug;
		[DataMember]
		public int bugId;
		[DataMember]
		public BugAccess userToBugAccess;

		public static List<UserInfo> init(List<Users> users, List<BugsOnUsers> bugsOnUsers)
		{
			List<UserInfo> userInfoList = new List<UserInfo>();
			foreach (Users us in users)
			{
				UserInfo uinfo = new UserInfo() { userId = us.id, userLogin = us.login };
				initByBugsOnUsersInfo(uinfo, getBugsOnUsersByUserId(us.id, bugsOnUsers));
				userInfoList.Add(uinfo);
			}
			return userInfoList;
		}

		protected static BugsOnUsers getBugsOnUsersByUserId(int userId, List<BugsOnUsers> bugsOnUsers)
		{
			foreach (BugsOnUsers bou in bugsOnUsers)
				if (bou.userId == userId)
					return bou;
			return null;
		}

		protected static void initByBugsOnUsersInfo(UserInfo ui, BugsOnUsers bou)
		{
			if (bou != null)
			{
				ui.isRelatedToCurrentBug = true;
				ui.bugId = bou.bugId;
				ui.userToBugAccess = (BugAccess)Enum.Parse(typeof(BugAccess), bou.access);
			}
			else
			{
				ui.isRelatedToCurrentBug = false;
				ui.bugId = -1;
				ui.userToBugAccess = BugAccess.none;
			}
		}
	}

	[DataContract]
	public enum BugAccess
	{
		[EnumMember]
		none,
		[EnumMember]
		read,
		[EnumMember]
		edit,
		[EnumMember]
		own
	}

	[DataContract]
	public enum BugStatus
	{
		[EnumMember]
		active,
		[EnumMember]
		closed
	}

	[DataContract]
	public enum LoginStatusEnum 
	{ 
		[EnumMember]
		OK,
 		[EnumMember]
		WrongLoginOrPassword,
		[EnumMember]
 		AlreadyLogined,
		[EnumMember]
		NotLogined
	}
}
